/**
 * @author		Ben Layet
 * @version 	1.0
 * @date		24 Sep 2008
 */
package org.ignoramus.application.model;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Implements the memorisation process model.
 */
public class KnowledgeFactory
{
	/** Duration after which the memory starts to fade, after first review : 1 hour */
	private static final long	first_decline_time = 1000 * 60 * 60;
	
	/** Map of study event to acquisition type. */
	private static final Map<StudyEvent, AcquisitionStatus> aquisitionMap = new HashMap<StudyEvent, AcquisitionStatus>();
	
	static
	{
		//initialise the acquisition map
		aquisitionMap.put(StudyEvent.PARTIALLY_CORRECT_TEST, AcquisitionStatus.PARTIALLY_KNOWN);
		aquisitionMap.put(StudyEvent.INCORRECT_TEST, AcquisitionStatus.UNKNOWN);
		aquisitionMap.put(StudyEvent.CORRECT_TEST, AcquisitionStatus.KNOWN);
		aquisitionMap.put(StudyEvent.REVIEW, AcquisitionStatus.KNOWN);
	}
	
	/**
	 * Creates a knowledge object based on past tests and study events.
	 * @param log the log of past study events.
	 * @return a knowledge object.
	 */
	public static Knowledge createKnowledge(StudyEventsLog log)
	{
		//sets the acquisition
		AcquisitionStatus acquisition	= getAcquisition(log);
		
		//the fading level
		byte fadingLevel = 0;
		
		//if the card is considered as known, we check the fading level
		if(AcquisitionStatus.KNOWN == acquisition)
		{
			//How long we have continuously got CORRECT at the tests?
			//this will indicate how much our memory has faded since the last test.
			
			//get the date of the last event
			Date continuousSuccessEndDate = log.lastKey();
			//First time we got it correct in the last success row... (= date of last event by default)
			Date continuousSuccessStartDate = continuousSuccessEndDate;
			
			//loop through the log starting from the last date.
			for (Iterator<Date> iterator = log.descendingKeySet().iterator(); iterator.hasNext() ;)
			{
				//get the date of the log event
				Date date = iterator.next();
				//check the event
				if(log.get(date) == StudyEvent.CORRECT_TEST)
				{
					continuousSuccessStartDate = date;
				}
				else
				{
					//end of the success row, break the loop
					break;
				}
			}
			//get the fading level 
			fadingLevel = getFadingLevel(continuousSuccessStartDate, continuousSuccessEndDate);
		}
		
		//return the knowledge object
		return new Knowledge(fadingLevel, acquisition);
	}

	/**
	 * Get the acquisition status.
	 * @param log the study events log
	 * @return the acquisition status.
	 */
	public static AcquisitionStatus getAcquisition(StudyEventsLog log)
	{
		//sets the default acquisition
		AcquisitionStatus acquisition	= AcquisitionStatus.UNTESTED;
		//check if there is any event
		if(log.size() > 0)
		{
			//get the last event of the log
			StudyEvent lastEvent = log.lastEntry().getValue();
			//sets the correct acquisition
			acquisition =	aquisitionMap.get(lastEvent);
		}
		//return the result
		return acquisition;
	}


	/**
	 * Calculates the fading level based on 
	 * how long we have continuously got CORRECT at the tests.
	 * 
	 * @param continuousSuccessStartDate start date of the success row.
	 * @param continuousSuccessEndDate end date of the success row.
	 * @return a number corresponding to the fading level (0 = no fading)
	 */
	public static byte getFadingLevel(Date continuousSuccessStartDate, Date continuousSuccessEndDate)
	{
		//get the duration of the success row
		long successDuration = continuousSuccessEndDate.getTime() - continuousSuccessStartDate.getTime();
		//any duration less than one hour is considered the same as one hour
		successDuration = Math.max(first_decline_time, successDuration);
		//get the duration elapsed since the success row ended
		long nonTestedSince = (new Date().getTime() - continuousSuccessEndDate.getTime()) + 1;
		//secret formula
		double dRatio = Math.sqrt(nonTestedSince  / successDuration);
		//return the ratio (meaningful values from 0 to 4)
		return (byte) Math.min(127, dRatio);
	}
}
